package com.cyys.controller;


import com.cyys.config.WxPayConfig;
import com.google.gson.Gson;

import com.cyys.config.HttpUtils;
import com.cyys.config.WechatPay2ValidatorForRequest;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.log4j.Log4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Random;
import java.util.UUID;


/**
 * @author Administrator
 * @description controller
 * @date 2022-07-06 9:15
 */
@RestController
@CrossOrigin
public class PayController {

    @Resource
    private WxPayConfig wxPayConfig;

    @Resource
    private CloseableHttpClient wxPayClient;

    @Resource
    private Verifier verifier;



    @GetMapping("/onLogin")
    public String onLogin(HttpServletRequest request){

        String js_code = request.getParameter("code");//前端发起请求携带上面获取到的code，后端接收

        //app Secret是小程序密钥（在mp.weixin.qq.com中的开发管理-》开发设置-》AppSecret(小程序密钥)中设置）
        System.err.println(wxPayConfig.getAppid());
        String baseUrl="https://api.weixin.qq.com/sns/jscode2session?appid="+wxPayConfig.getAppid()+"&secret="+wxPayConfig.getAppSecret()
                +"&js_code="+js_code+"&grant_type=authorization_code";
        String res=null;
        try {
            //这里发起请求获取到session-key，和openid
            res = requestByGetMethod(baseUrl).split("/n")[0];
            System.out.println(res);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("res:"+res);
        return res;//返回给前端
    }


    /**
     * 模拟发送url Get 请求
     * @param url
     * @return
     */
    public String requestByGetMethod(String url) {
        System.err.println("get请求");
        CloseableHttpClient httpClient = HttpClients.createDefault();
        StringBuilder entityStringBuilder = null;
        try {
            HttpGet get = new HttpGet(url);
            CloseableHttpResponse httpResponse = null;
            httpResponse = httpClient.execute(get);
            try {
                HttpEntity entity = httpResponse.getEntity();
                entityStringBuilder = new StringBuilder();
                if (null != entity) {
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent(), "UTF-8"), 8 * 1024);
                    String line = null;
                    while ((line = bufferedReader.readLine()) != null) {
                        entityStringBuilder.append(line + "/n");
                    }
                }
            } finally {
                httpResponse.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return entityStringBuilder.toString();
    }



    @ResponseBody
    @RequestMapping("/returnparam")
    public HashMap<String, String> doOrder(HttpServletRequest request, HttpServletResponse response) throws Exception{
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        //得到openid（微信用户唯一的openid）
        String openid = request.getParameter("openid");
        //得到价钱（自定义）
        int fee = 0;//单位是分
        if (null != request.getParameter("price")) {
            fee = Integer.parseInt(request.getParameter("price").toString())/10;
        }
        //得到商品的ID（自定义）
        String goodsid=request.getParameter("goodsid");
        //订单标题（自定义）
        String title = request.getParameter("title");
        //时间戳，
        String times = System.currentTimeMillis() + "";

        //订单编号（自定义 这里以时间戳+随机数）
        Random random = new Random();
        String did = times+random.nextInt(1000);

        System.out.println("生成订单");
        //调用统一下单API
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        // 请求body参数
        Gson gson = new Gson();
        HashMap<Object, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid",wxPayConfig.getAppid());//appid
        paramsMap.put("mchid",wxPayConfig.getMchId());//商户号
        paramsMap.put("description",title);//商品描述
        paramsMap.put("out_trade_no",did);//商户订单号
        paramsMap.put("notify_url","http://d4a93w.natappfree.cc/wxBuy");//通知地址，可随便写，如果不需要通知的话，不影响支付，但是影响后续修改订单状态

        //订单金额
        HashMap<Object, Object> amountMap = new HashMap<>();
        amountMap.put("total",fee);//金额
        amountMap.put("currency","CNY");//货币类型
        paramsMap.put("amount",amountMap);

        //支付者
        HashMap<Object, Object> playerMap = new HashMap<>();
        playerMap.put("openid",openid);

        paramsMap.put("payer",playerMap);

        //将参数转化未json字符串
        String jsonParamsMap = gson.toJson(paramsMap);
        System.out.println("请求参数："+jsonParamsMap);

        StringEntity entity = new StringEntity(jsonParamsMap,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse resp = wxPayClient.execute(httpPost);

        try {
            int statusCode = resp.getStatusLine().getStatusCode();
            String bodyAsString = EntityUtils.toString(resp.getEntity());
            if (statusCode == 200) { //处理成功
                System.out.println("成功，返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                System.out.println("成功");
            } else {
                System.out.println("小程序下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            //相应结果
            HashMap<String,String> resultMap = gson.fromJson(bodyAsString, HashMap.class);


            //获取prepay—id
            String prepayId = resultMap.get("prepay_id");


            //获取到perpayid之后需要对数据进行二次封装，前端调起支付必须存在的参数
            HashMap<String, String> payMap = new HashMap<>();
            payMap.put("appid",wxPayConfig.getAppid());//appid
            long currentTimestamp = System.currentTimeMillis();//时间戳，别管那么多，他就是需要
            payMap.put("timeStamp",currentTimestamp+"");
            String nonceStr = UUID.randomUUID().toString()
                    .replaceAll("-", "")
                    .substring(0, 32);;//随机字符串，别管那么多他就是需要，要咱就给
            payMap.put("nonceStr",nonceStr);
            //apiv3只支持这种加密方式
            payMap.put("signType","RSA");

            payMap.put("package","prepay_id="+prepayId);


            //通过appid，timeStamp，nonceStr，signType，package以及商户密钥进行key=value形式进行拼接加密
            //加密方法我会放在这个代码段段下面
            WxPayConfig wxPayConfig=new WxPayConfig();
            String aPackage = wxPayConfig.buildMessageTwo("wxa10fbb469a5b478a", currentTimestamp, nonceStr, payMap.get("package"));


            //获取对应的签名
            //加密方法我会放在这个代码段段下面
            String paySign = wxPayConfig.sign("apiclient_key.pem",aPackage.getBytes("utf-8"));

            System.out.println(3);
            payMap.put("paySign",paySign);

            /**
             *	在这里你可以加入自己的数据库操作，存储一条订单信息，状态为未支付就行了
             *	在这里你可以加入自己的数据库操作，存储一条订单信息，状态为未支付就行了
             *	在这里你可以加入自己的数据库操作，存储一条订单信息，状态为未支付就行了
             */
            System.out.println("给前端的玩意："+payMap);;//前端会根据这些参数调起支付页面
            //到这里，就已经完成了官网图中的第8步了
            return payMap;

        }finally {
            resp.close();
        }
    }

    @PostMapping("/wxBuy")
    public String wxBuy(HttpServletRequest request,HttpServletResponse response){

        Gson gson = new Gson();
        //创建一个应答对象

        HashMap<String, String> map = new HashMap<>();

        try {
            //处理通知参数
            String body = HttpUtils.readData(request);
            HashMap<String,Object> bodyMap = gson.fromJson(body, HashMap.class);

            String requestId = (String) bodyMap.get("id");

            //log.info("支付通知的id=====》》》{}",bodyMap.get("id"));
//            log.info("支付通知的完整数据=====》》》{}",body);

            //TODO : 签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId,body);
            if (!wechatPay2ValidatorForRequest.validate(request)) {
                //log.error("通知验签失败");
                //通知失败应答
                response.setStatus(500);
                map.put("code","ERROR");
                map.put("message","通知验签失败");
                return gson.toJson(map);
            }
            //log.info("通知验签成功");
            //TODO : 处理订单
            //这里可以调用你要处理业务逻辑的service，我这里就不写了，
            //然后解密数据在业务service中调用就行，我在这里就直接调用了

            //解密密文，获取明文
            String plaintText = wxPayConfig.decryptFromResource(bodyMap);
            //将明文转为map
            HashMap plaintTextMap = gson.fromJson(plaintText, HashMap.class);


            //获取支付下单的时候，传入的商户订单号,可以根据这个订单号去获取我们的一个订单记录，从而更新订单状态
            String orderNo = (String) plaintTextMap.get("out_trade_no");
            //业务编号
            String transactionId = (String) plaintTextMap.get("transaction_id");
            //trade_type，支付类型,如果有需要的话， 你可以存储在数据库中，这里我们的数据，基本上都是JSapi支付类型
            String tradeType = (String) plaintTextMap.get("trade_type");
            //交易状态
            String tradeState = (String) plaintTextMap.get("trade_state");
            //还有很多，为这里就不一一去写了



            /**
             * 在更新你订单状态之前，可以先根据orderNo，查询数据库中是否有这个订单
             * 然后查询这个订单是否已经被处理过，也就是状态是否是已经支付的状态
             * 如果这个订单已经被处理了，那么我们可以直接return，没有被处理过我们在处理
             * 这样可以避免数据库被反复的操作
             *
             * 微信官方的解释是：
             *  同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
             *  推荐的做法是，当商户系统收到通知进行处理时，先检查对应业务数据的状态，
             *  并判断该通知是否已经处理。如果未处理，则再进行处理；如果已处理，
             *  则直接返回结果成功。在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，
             *  以避免函数重入造成的数据混乱。
             */



            //更新订单状态
            /*
             * 你的数据库操作
             * 一定要存储解密出来的transaction_id字段在数据库中
             * 如果需要跟微信官方对账的话，是需要提供这个字段进行一个查账的
             * */


            //成功应答
            response.setStatus(200);
            map.put("code","SUCCESS");
            map.put("message","成功");
            return gson.toJson(map);
        } catch (Exception e) {
            e.printStackTrace();
            //失败应答
            response.setStatus(500);
            map.put("code","ERROR");
            map.put("message","失败");
            return gson.toJson(map);
        }
    }
}
